/**
 * @license Highstock JS v7.2.0 (2019-09-03)
 *
 * Indicator series type for Highstock
 *
 * (c) 2010-2019 Paweł Dalek
 *
 * License: www.highcharts.com/license
 */
'use strict';
(function (factory) {
    if (typeof module === 'object' && module.exports) {
        factory['default'] = factory;
        module.exports = factory;
    } else if (typeof define === 'function' && define.amd) {
        define('highcharts/indicators/volume-by-price', ['highcharts', 'highcharts/modules/stock'], function (Highcharts) {
            factory(Highcharts);
            factory.Highcharts = Highcharts;
            return factory;
        });
    } else {
        factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
    }
}(function (Highcharts) {
    var _modules = Highcharts ? Highcharts._modules : {};
    function _registerModule(obj, path, args, fn) {
        if (!obj.hasOwnProperty(path)) {
            obj[path] = fn.apply(null, args);
        }
    }
    _registerModule(_modules, 'indicators/volume-by-price.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
        /* *
         *
         *  (c) 2010-2019 Paweł Dalek
         *
         *  Volume By Price (VBP) indicator for Highstock
         *
         *  License: www.highcharts.com/license
         *
         * */



        var isArray = U.isArray;

        // Utils
        function arrayExtremesOHLC(data) {
            var dataLength = data.length,
                min = data[0][3],
                max = min,
                i = 1,
                currentPoint;

            for (; i < dataLength; i++) {
                currentPoint = data[i][3];
                if (currentPoint < min) {
                    min = currentPoint;
                }

                if (currentPoint > max) {
                    max = currentPoint;
                }
            }

            return {
                min: min,
                max: max
            };
        }

        var abs = Math.abs,
            noop = H.noop,
            addEvent = H.addEvent,
            correctFloat = H.correctFloat,
            seriesType = H.seriesType,
            columnPrototype = H.seriesTypes.column.prototype;

        /**
         * The Volume By Price (VBP) series type.
         *
         * @private
         * @class
         * @name Highcharts.seriesTypes.vbp
         *
         * @augments Highcharts.Series
         */
        seriesType(
            'vbp',
            'sma',
            /**
             * Volume By Price indicator.
             *
             * This series requires `linkedTo` option to be set.
             *
             * @sample stock/indicators/volume-by-price
             *         Volume By Price indicator
             *
             * @extends      plotOptions.sma
             * @since        6.0.0
             * @product      highstock
             * @optionparent plotOptions.vbp
             */
            {
                /**
                 * @excluding index, period
                 */
                params: {
                    /**
                     * The number of price zones.
                     */
                    ranges: 12,
                    /**
                     * The id of volume series which is mandatory. For example using
                     * OHLC data, volumeSeriesID='volume' means the indicator will be
                     * calculated using OHLC and volume values.
                     */
                    volumeSeriesID: 'volume'
                },
                /**
                 * The styles for lines which determine price zones.
                 */
                zoneLines: {
                    /**
                     * Enable/disable zone lines.
                     */
                    enabled: true,
                    /**
                     * Specify the style of zone lines.
                     *
                     * @type    {Highcharts.CSSObject}
                     * @default {"color": "#0A9AC9", "dashStyle": "LongDash", "lineWidth": 1}
                     */
                    styles: {
                        /** @ignore-options */
                        color: '#0A9AC9',
                        /** @ignore-options */
                        dashStyle: 'LongDash',
                        /** @ignore-options */
                        lineWidth: 1
                    }
                },
                /**
                 * The styles for bars when volume is divided into positive/negative.
                 */
                volumeDivision: {
                    /**
                     * Option to control if volume is divided.
                     */
                    enabled: true,
                    styles: {
                        /**
                         * Color of positive volume bars.
                         *
                         * @type {Highcharts.ColorString}
                         */
                        positiveColor: 'rgba(144, 237, 125, 0.8)',
                        /**
                         * Color of negative volume bars.
                         *
                         * @type {Highcharts.ColorString}
                         */
                        negativeColor: 'rgba(244, 91, 91, 0.8)'
                    }
                },
                // To enable series animation; must be animationLimit > pointCount
                animationLimit: 1000,
                enableMouseTracking: false,
                pointPadding: 0,
                zIndex: -1,
                crisp: true,
                dataGrouping: {
                    enabled: false
                },
                dataLabels: {
                    /** @ignore-option */
                    allowOverlap: true,
                    /** @ignore-option */
                    enabled: true,
                    /** @ignore-option */
                    format: 'P: {point.volumePos:.2f} | N: {point.volumeNeg:.2f}',
                    /** @ignore-option */
                    padding: 0,
                    /** @ignore-option */
                    style: {
                        fontSize: '7px'
                    },
                    /** @ignore-option */
                    verticalAlign: 'top'
                }
            },
            /**
             * @lends Highcharts.Series#
             */
            {
                nameBase: 'Volume by Price',
                bindTo: {
                    series: false,
                    eventName: 'afterSetExtremes'
                },
                calculateOn: 'render',
                markerAttribs: noop,
                drawGraph: noop,
                getColumnMetrics: columnPrototype.getColumnMetrics,
                crispCol: columnPrototype.crispCol,
                init: function (chart) {
                    var indicator = this,
                        params,
                        baseSeries,
                        volumeSeries;

                    H.seriesTypes.sma.prototype.init.apply(indicator, arguments);

                    params = indicator.options.params;
                    baseSeries = indicator.linkedParent;
                    volumeSeries = chart.get(params.volumeSeriesID);

                    indicator.addCustomEvents(baseSeries, volumeSeries);

                    return indicator;
                },
                // Adds events related with removing series
                addCustomEvents: function (baseSeries, volumeSeries) {
                    var indicator = this;

                    function toEmptyIndicator() {
                        indicator.chart.redraw();

                        indicator.setData([]);
                        indicator.zoneStarts = [];

                        if (indicator.zoneLinesSVG) {
                            indicator.zoneLinesSVG.destroy();
                            delete indicator.zoneLinesSVG;
                        }
                    }

                    // If base series is deleted, indicator series data is filled with
                    // an empty array
                    indicator.dataEventsToUnbind.push(
                        addEvent(baseSeries, 'remove', function () {
                            toEmptyIndicator();
                        })
                    );

                    // If volume series is deleted, indicator series data is filled with
                    // an empty array
                    if (volumeSeries) {
                        indicator.dataEventsToUnbind.push(
                            addEvent(volumeSeries, 'remove', function () {
                                toEmptyIndicator();
                            })
                        );
                    }

                    return indicator;
                },
                // Initial animation
                animate: function (init) {
                    var series = this,
                        attr = {};

                    if (H.svg && !init) {
                        attr.translateX = series.yAxis.pos;
                        series.group.animate(
                            attr,
                            H.extend(H.animObject(series.options.animation), {
                                step: function (val, fx) {
                                    series.group.attr({
                                        scaleX: Math.max(0.001, fx.pos)
                                    });
                                }
                            })
                        );

                        // Delete this function to allow it only once
                        series.animate = null;
                    }
                },
                drawPoints: function () {
                    var indicator = this;

                    if (indicator.options.volumeDivision.enabled) {
                        indicator.posNegVolume(true, true);
                        columnPrototype.drawPoints.apply(indicator, arguments);
                        indicator.posNegVolume(false, false);
                    }

                    columnPrototype.drawPoints.apply(indicator, arguments);
                },
                // Function responsible for dividing volume into positive and negative
                posNegVolume: function (initVol, pos) {
                    var indicator = this,
                        signOrder = pos ?
                            ['positive', 'negative'] :
                            ['negative', 'positive'],
                        volumeDivision = indicator.options.volumeDivision,
                        pointLength = indicator.points.length,
                        posWidths = [],
                        negWidths = [],
                        i = 0,
                        pointWidth,
                        priceZone,
                        wholeVol,
                        point;

                    if (initVol) {
                        indicator.posWidths = posWidths;
                        indicator.negWidths = negWidths;
                    } else {
                        posWidths = indicator.posWidths;
                        negWidths = indicator.negWidths;
                    }

                    for (; i < pointLength; i++) {
                        point = indicator.points[i];
                        point[signOrder[0] + 'Graphic'] = point.graphic;
                        point.graphic = point[signOrder[1] + 'Graphic'];

                        if (initVol) {
                            pointWidth = point.shapeArgs.width;
                            priceZone = indicator.priceZones[i];
                            wholeVol = priceZone.wholeVolumeData;

                            if (wholeVol) {
                                posWidths.push(
                                    pointWidth / wholeVol * priceZone.positiveVolumeData
                                );
                                negWidths.push(
                                    pointWidth / wholeVol * priceZone.negativeVolumeData
                                );
                            } else {
                                posWidths.push(0);
                                negWidths.push(0);
                            }
                        }

                        point.color = pos ?
                            volumeDivision.styles.positiveColor :
                            volumeDivision.styles.negativeColor;
                        point.shapeArgs.width = pos ?
                            indicator.posWidths[i] :
                            indicator.negWidths[i];
                        point.shapeArgs.x = pos ?
                            point.shapeArgs.x :
                            indicator.posWidths[i];
                    }
                },
                translate: function () {
                    var indicator = this,
                        options = indicator.options,
                        chart = indicator.chart,
                        yAxis = indicator.yAxis,
                        yAxisMin = yAxis.min,
                        zoneLinesOptions = indicator.options.zoneLines,
                        priceZones = indicator.priceZones,
                        yBarOffset = 0,
                        indicatorPoints,
                        volumeDataArray,
                        maxVolume,
                        primalBarWidth,
                        barHeight,
                        barHeightP,
                        oldBarHeight,
                        barWidth,
                        pointPadding,
                        chartPlotTop,
                        barX,
                        barY;

                    columnPrototype.translate.apply(indicator);
                    indicatorPoints = indicator.points;

                    // Do translate operation when points exist
                    if (indicatorPoints.length) {
                        pointPadding = options.pointPadding < 0.5 ?
                            options.pointPadding :
                            0.1;
                        volumeDataArray = indicator.volumeDataArray;
                        maxVolume = H.arrayMax(volumeDataArray);
                        primalBarWidth = chart.plotWidth / 2;
                        chartPlotTop = chart.plotTop;
                        barHeight = abs(yAxis.toPixels(yAxisMin) -
                            yAxis.toPixels(yAxisMin + indicator.rangeStep));
                        oldBarHeight = abs(yAxis.toPixels(yAxisMin) -
                            yAxis.toPixels(yAxisMin + indicator.rangeStep));

                        if (pointPadding) {
                            barHeightP = abs(barHeight * (1 - 2 * pointPadding));
                            yBarOffset = abs((barHeight - barHeightP) / 2);
                            barHeight = abs(barHeightP);
                        }

                        indicatorPoints.forEach(function (point, index) {
                            barX = point.barX = point.plotX = 0;
                            barY = point.plotY = (
                                yAxis.toPixels(priceZones[index].start) -
                                chartPlotTop -
                                (
                                    yAxis.reversed ?
                                        (barHeight - oldBarHeight) :
                                        barHeight
                                ) -
                                yBarOffset
                            );
                            barWidth = correctFloat(
                                primalBarWidth *
                                priceZones[index].wholeVolumeData / maxVolume
                            );
                            point.pointWidth = barWidth;

                            point.shapeArgs = indicator.crispCol.apply( // eslint-disable-line no-useless-call
                                indicator,
                                [barX, barY, barWidth, barHeight]
                            );

                            point.volumeNeg = priceZones[index].negativeVolumeData;
                            point.volumePos = priceZones[index].positiveVolumeData;
                            point.volumeAll = priceZones[index].wholeVolumeData;
                        });

                        if (zoneLinesOptions.enabled) {
                            indicator.drawZones(
                                chart,
                                yAxis,
                                indicator.zoneStarts,
                                zoneLinesOptions.styles
                            );
                        }
                    }
                },
                getValues: function (series, params) {
                    var indicator = this,
                        xValues = series.processedXData,
                        yValues = series.processedYData,
                        chart = indicator.chart,
                        ranges = params.ranges,
                        VBP = [],
                        xData = [],
                        yData = [],
                        isOHLC,
                        volumeSeries,
                        priceZones;

                    // Checks if base series exists
                    if (!series.chart) {
                        return H.error(
                            'Base series not found! In case it has been removed, add ' +
                            'a new one.',
                            true,
                            chart
                        );
                    }

                    // Checks if volume series exists
                    if (!(volumeSeries = chart.get(params.volumeSeriesID))) {
                        return H.error(
                            'Series ' +
                            params.volumeSeriesID +
                            ' not found! Check `volumeSeriesID`.',
                            true,
                            chart
                        );
                    }

                    // Checks if series data fits the OHLC format
                    isOHLC = isArray(yValues[0]);

                    if (isOHLC && yValues[0].length !== 4) {
                        return H.error(
                            'Type of ' +
                            series.name +
                            ' series is different than line, OHLC or candlestick.',
                            true,
                            chart
                        );
                    }

                    // Price zones contains all the information about the zones (index,
                    // start, end, volumes, etc.)
                    priceZones = indicator.priceZones = indicator.specifyZones(
                        isOHLC,
                        xValues,
                        yValues,
                        ranges,
                        volumeSeries
                    );

                    priceZones.forEach(function (zone, index) {
                        VBP.push([zone.x, zone.end]);
                        xData.push(VBP[index][0]);
                        yData.push(VBP[index][1]);
                    });

                    return {
                        values: VBP,
                        xData: xData,
                        yData: yData
                    };
                },
                // Specifing where each zone should start ans end
                specifyZones: function (
                    isOHLC,
                    xValues,
                    yValues,
                    ranges,
                    volumeSeries
                ) {
                    var indicator = this,
                        rangeExtremes = isOHLC ? arrayExtremesOHLC(yValues) : false,
                        lowRange = rangeExtremes ?
                            rangeExtremes.min :
                            H.arrayMin(yValues),
                        highRange = rangeExtremes ?
                            rangeExtremes.max :
                            H.arrayMax(yValues),
                        zoneStarts = indicator.zoneStarts = [],
                        priceZones = [],
                        i = 0,
                        j = 1,
                        rangeStep,
                        zoneStartsLength;

                    if (!lowRange || !highRange) {
                        if (this.points.length) {
                            this.setData([]);
                            this.zoneStarts = [];
                            this.zoneLinesSVG.destroy();
                        }
                        return [];
                    }

                    rangeStep = indicator.rangeStep =
                        correctFloat(highRange - lowRange) / ranges;
                    zoneStarts.push(lowRange);

                    for (; i < ranges - 1; i++) {
                        zoneStarts.push(correctFloat(zoneStarts[i] + rangeStep));
                    }

                    zoneStarts.push(highRange);
                    zoneStartsLength = zoneStarts.length;

                    //    Creating zones
                    for (; j < zoneStartsLength; j++) {
                        priceZones.push({
                            index: j - 1,
                            x: xValues[0],
                            start: zoneStarts[j - 1],
                            end: zoneStarts[j]
                        });
                    }

                    return indicator.volumePerZone(
                        isOHLC,
                        priceZones,
                        volumeSeries,
                        xValues,
                        yValues
                    );
                },
                // Calculating sum of volume values for a specific zone
                volumePerZone: function (
                    isOHLC,
                    priceZones,
                    volumeSeries,
                    xValues,
                    yValues
                ) {
                    var indicator = this,
                        volumeXData = volumeSeries.processedXData,
                        volumeYData = volumeSeries.processedYData,
                        lastZoneIndex = priceZones.length - 1,
                        baseSeriesLength = yValues.length,
                        volumeSeriesLength = volumeYData.length,
                        previousValue,
                        startFlag,
                        endFlag,
                        value,
                        i;

                    // Checks if each point has a corresponding volume value
                    if (abs(baseSeriesLength - volumeSeriesLength)) {
                        // If the first point don't have volume, add 0 value at the
                        // beggining of the volume array
                        if (xValues[0] !== volumeXData[0]) {
                            volumeYData.unshift(0);
                        }

                        // If the last point don't have volume, add 0 value at the end
                        // of the volume array
                        if (
                            xValues[baseSeriesLength - 1] !==
                            volumeXData[volumeSeriesLength - 1]
                        ) {
                            volumeYData.push(0);
                        }
                    }

                    indicator.volumeDataArray = [];

                    priceZones.forEach(function (zone) {
                        zone.wholeVolumeData = 0;
                        zone.positiveVolumeData = 0;
                        zone.negativeVolumeData = 0;

                        for (i = 0; i < baseSeriesLength; i++) {
                            startFlag = false;
                            endFlag = false;
                            value = isOHLC ? yValues[i][3] : yValues[i];
                            previousValue = i ?
                                (isOHLC ? yValues[i - 1][3] : yValues[i - 1]) :
                                value;

                            // Checks if this is the point with the lowest close value
                            // and if so, adds it calculations
                            if (value <= zone.start && zone.index === 0) {
                                startFlag = true;
                            }

                            // Checks if this is the point with the highest close value
                            // and if so, adds it calculations
                            if (value >= zone.end && zone.index === lastZoneIndex) {
                                endFlag = true;
                            }

                            if (
                                (value > zone.start || startFlag) &&
                                (value < zone.end || endFlag)
                            ) {
                                zone.wholeVolumeData += volumeYData[i];

                                if (previousValue > value) {
                                    zone.negativeVolumeData += volumeYData[i];
                                } else {
                                    zone.positiveVolumeData += volumeYData[i];
                                }
                            }
                        }
                        indicator.volumeDataArray.push(zone.wholeVolumeData);
                    });

                    return priceZones;
                },
                // Function responsoble for drawing additional lines indicating zones
                drawZones: function (chart, yAxis, zonesValues, zonesStyles) {
                    var indicator = this,
                        renderer = chart.renderer,
                        zoneLinesSVG = indicator.zoneLinesSVG,
                        zoneLinesPath = [],
                        leftLinePos = 0,
                        rightLinePos = chart.plotWidth,
                        verticalOffset = chart.plotTop,
                        verticalLinePos;

                    zonesValues.forEach(function (value) {
                        verticalLinePos = yAxis.toPixels(value) - verticalOffset;
                        zoneLinesPath = zoneLinesPath.concat(chart.renderer.crispLine([
                            'M',
                            leftLinePos,
                            verticalLinePos,
                            'L',
                            rightLinePos,
                            verticalLinePos
                        ], zonesStyles.lineWidth));
                    });

                    // Create zone lines one path or update it while animating
                    if (zoneLinesSVG) {
                        zoneLinesSVG.animate({
                            d: zoneLinesPath
                        });
                    } else {
                        zoneLinesSVG = indicator.zoneLinesSVG =
                            renderer.path(zoneLinesPath).attr({
                                'stroke-width': zonesStyles.lineWidth,
                                'stroke': zonesStyles.color,
                                'dashstyle': zonesStyles.dashStyle,
                                'zIndex': indicator.group.zIndex + 0.1
                            })
                                .add(indicator.group);
                    }
                }
            },
            /**
             * @lends Highcharts.Point#
             */
            {
                // Required for destroying negative part of volume
                destroy: function () {
                    if (this.negativeGraphic) {
                        this.negativeGraphic = this.negativeGraphic.destroy();
                    }
                    return H.Point.prototype.destroy.apply(this, arguments);
                }
            }
        );

        /**
         * A `Volume By Price (VBP)` series. If the [type](#series.vbp.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         *
         * @extends   series,plotOptions.vbp
         * @since     6.0.0
         * @product   highstock
         * @excluding dataParser, dataURL
         * @apioption series.vbp
         */

    });
    _registerModule(_modules, 'masters/indicators/volume-by-price.src.js', [], function () {


    });
}));